<?php
/**
 * Processor.php - 27 juli 2009
 * @author Kevin Breed
 * 
 * Basic config processor, that is only used to provide extra funtionality for
 * loaded config settings. 
 */

class KB_Config_Processor
{
	protected static 	$_inst = null;
	protected 			$_replacedPatterns = array();
	
	/**
	 * Rather call getInstance() instead of this constructor, because this is a
	 * singleton class.
	 */
	public function __construct ()
	{
		if ( self::$_inst != null ) return self::getInstance();
	}
	
	/**
	 * Returns an instance of itself.
	 *
	 * @return KB_Config_Processor
	 */
	public static function getInstance ()
	{
		if ( self::$_inst == null )
			 self::$_inst = new self();
		
		return self::$_inst;
	}

	/**
	 * Process the given config values, so variables inside a value will be 
	 * replaced with their corresponding value.
	 *
	 * Variable names must be enclosed by percent signs when they are in the 
	 * same section or in the toplevel category, like %setting%. This will 
	 * use the value of %setting% wherever it is used as value. For example:
	 *
	 * toplevel => topValue
	 *
	 * [section]
	 * section_setting 	=> sectionValue
	 * replace_top 		=> %toplevel% < this will be replaced by 'topValue'
	 * replace_section	=> %section_setting% < this will be replaced by 'sectionValue'
	 *
	 * A section name followed by a double collon (::) is needed when a setting 
	 * from another section is used. This also works for nested sections. 
	 * For example: 
	 * 
	 * [general]
	 * settingName = generalvalue
	 *
	 * [otherSection]
	 * someSetting = %general::settingName% < this will be replaced by 'generalValue' 
	 *
	 * @param 	array $config 	An array with config values, setup as key => value
	 * 							pairs. Values can be nested arrays.
	 * @return	void
	 */
	public function processValues ( $config )
	{
		return $this->_processValues( $config, $config );
	}
	
	/**
	 * Process values.
	 *
	 * @param 	string|array 	$value 			The value to process
	 * @param 	array 			$baseArray		The entire array with config values
	 * @param 	array 			$parentArray	The parent array with config values
	 * @return 	string|array 					When the given value is an array
	 * 											the same, but processed array 
	 * 											will be returned. When it is a
	 * 											string the same but processed
	 * 											value will be returned.
	 */
	protected function _processValues 
	( 
		$value, 
		$baseArray=array(), 
		$parentArray=array() 
	)
	{
		if ( is_array( $value ) ) 
		{
			$newArr = array();
			
			// iterate over new section
			foreach ( $value as $cKey => $cVal )
				$newArr[ $cKey ] = $this->_processValues( $cVal, $baseArray, $value );
			
			return $newArr;
		}
		
		elseif ( is_string( $value ) ) 
		{
			// process value
			$newVal = $this->_replaceVars( $value, $baseArray, $parentArray );
			
			// return new value if something is replaced, or return the same 
			// otherwise
			return ( $newVal !== null ) ? $newVal : $value;
			
		}
	}
	
	/**
	 * Replaces variables inside a value with their corresponding value.
	 *
	 * @param 	string 	$rawValue 		The value to replace.
	 * @param	array 	$baseArray
	 * @param	array 	$parentArray
	 * @return 	string					The replaced value, or null when nothing
	 * 									is replaced.
	 */
	protected function _replaceVars ( $rawValue, $baseArray, $parentArray )
	{
		$replaced = false;
		
		while ( $this->_hasVar( $rawValue ) )
		{
			$matches 		= array();
			$patterns 		= array();
			$replacements 	= array();
		
			preg_match_all( '/%([^% ]*)%/', 
							$rawValue, 
							$matches, 
							PREG_SET_ORDER );
			
			foreach ( $matches as $match )
			{				
				$pattern = $match[ 0 ];
				$varName = $match[ 1 ];
				
				// check if this pattern is already found
				if ( isset( $this->_replacedPatterns[ $pattern ] ) )
					 $replacement = $this->_replacedPatterns[ $pattern ];
				
				// else check for sections
				elseif ( strstr( $varName, '::' ) )
				{
					// section found, convert section path (section::value), to 
					// array path ($conf['section']['value'])
					$cnfArrVar = 	preg_replace
									( 
										'/::([^:]*)/', "['$1']", 
										'::' . $varName 
									);
					
					// create dynamic vars
					$bArrVarStr = "\$baseArray{$cnfArrVar}";
					$pArrVarStr = "\$parentArray{$cnfArrVar}";
					
					// evaluate dynamic vars
					eval( "if ( isset( $bArrVarStr ) ) \$replacement = $bArrVarStr;" );
					eval( "if ( isset( $pArrVarStr ) ) \$replacement = $pArrVarStr;" );
				}
				
				// no section
				else
				{
					// search in parent array
					if ( isset( $parentArray[ $varName ] ) )
						 $replacement = $parentArray[ $varName ];
					
					// then search in base array
					elseif ( isset( $baseArray[ $varName ] ) )
						$replacement = $baseArray[ $varName ]; 
					
					// then nothing is found
					else
						$replacement = "[$varName]";
				}
				
				// push to patterns / replacements array 				
				$patterns[] 	= "/$pattern/";
				$replacements[]	= $replacement;
				
				// save in replaced patterns array
				$this->_replacedPatterns[ $pattern ] = $replacement;
			}
			
			// replace value
			$rawValue = preg_replace( $patterns, $replacements, $rawValue );
			
			// give replace indication
			$replaced = true;
		}
		
		return ( $replaced ) ? $rawValue : null;
	}
	
	/**
	 * Checks if there are more vars in the given value.
	 *
	 * @param 	string 	A value to check
	 * @return 	bool	True when the string contains more vars, false otherwise
	 */
	protected function _hasVar ( $rawValue )
	{
		return ( preg_match( '/%([^% ]*)%/', $rawValue ) > 0 );
	}
}

?>
